<?php


namespace Aoe\Emulator\Pascal;

use Aoe\Emulator\Ifc\Type;
use Aoe\Emulator\Schema\Element\Independent;
use Aoe\Emulator\Schema\Element\Relation;
use Aoe\Database\Operator;
use Aoe\Database\Sprite;
use Aoe\Util\Exception;
use Aoe\Util\Validator;
use PDO;
use Throwable;

/**
 * # 数据库列映射
 *  * 根据元素的定义，验证数据和操作符的有效性
 */
abstract class Pascal
{
    const string ERR_OPERATOR = '${name}: 无效的指令[${operator}]';
    const string ERR_OPERAND = '${operator}@${name}: 无效的参数[${value}]';

    const string MIN = 'mix';
    const string MAX = 'max';
    const string LEN = 'len';

    public function __construct(protected  Independent $element) {}

    protected function bind(): int
    {
        return PDO::PARAM_STR;
    }

    /**
     * ## 输出修正
     *
     * @param $value
     *
     * @return mixed
     */
    public function display($value): mixed
    {
        return $value;
    }

    /**
     * ## 在从数据库拉出来后修正
     *
     * @param $value
     *
     * @return mixed
     */
    public function afterLoad($value): mixed
    {
        return $value;
    }

    /**
     * ## 保存到数据库前修正数据
     *
     * @param mixed $value
     * @return mixed
     */
    public function save(mixed $value): mixed
    {
        return $value;
    }

    /**
     * ## 在插入和更新时修正
     *
     * @param mixed $value
     * @return mixed
     */
    protected function before_input(mixed $value): mixed
    {
        return $value;
    }

    /**
     * ## 从输入读取后修正输入值类型，错误返回false
     *
     * @param        $value
     * @param string $operator
     * @param bool   $calculate
     *
     * @return mixed
     * @throws Exception
     */
    abstract protected function valid_core(&$value, string $operator = '=', bool $calculate = true): bool;

    /**
     * ## 生成数据库使用的Sprite
     *
     * @param string        $operator
     * @param               $value
     *
     * @return Sprite
     */
    protected function make_sprite(string $operator, $value): Sprite
    {
        return new Sprite(
            $operator,
            $value,
            $this->bind(),
            $this->element->getColumn(),
        );
    }

    protected function null_sprite(string $operator): Sprite
    {
        return new Sprite(
            $operator,
            null,
            PDO::PARAM_NULL,
            $this->element->getColumn(),
        );
    }

    protected function int_sprite(string $operator, $value): Sprite
    {
        return new Sprite(
            $operator,
            intval($value),
            PDO::PARAM_INT,
            $this->element->getColumn(),
        );
    }

    /**
     * ## 根据运算符校验参数
     *
     * @param mixed  $value
     * @param string $operator
     * @param bool   $calculate
     *
     * @return mixed
     * @throws Exception
     */
    protected function valid_fix(mixed &$value, string $operator, bool $calculate = true): mixed
    {
        if (!is_array($value)) return $this->valid_core($value, $operator, $calculate);

        if ($calculate && !Operator::isArrayCalculator($operator)) return false;
        if (!$calculate && !Operator::isArrayCompare($operator)) return false;

        return array_map(fn ($v) => $this->valid_core($v, $operator, $calculate), $value);
    }

    protected function valid_rules(mixed $value): bool
    {
        $rules = $this->element->get('rules', []);

        if (empty($rules)) return true;
        return !array_any($rules, fn($rule) => !$this->_valid_rule($rule, $value));
    }

    protected function _valid_rule(array $rule, mixed $value): bool
    {
        if(isset($rule['min'])) return Validator::validateMin($value, $rule['min']);
        if(isset($rule['max'])) return Validator::validateMax($value, $rule['max']);
        if(isset($rule['len'])) return Validator::validateLen($value, $rule['len']);

        if(isset($rule['url'])) return Validator::validateUrl($value);
        if(isset($rule['email'])) return Validator::validateEmail($value);

       return true;
    }

    ///////////////////////////////////////////////////////////////////////////////////////////////////


    /**
     * 生成计算表达式
     *
     * @param mixed|null $value
     * @param string|Operator|null $operator
     *
     * @param array|null $input
     *
     * @return ?Sprite
     * @throws Exception
     */
    public function calc(
        mixed                $value,
        string|Operator|null $operator,
        ?array               $input = null,
    ): ?Sprite
    {
        $element = $this->element;
        $name = $element->name;

        if ($operator === null) $operator = $this->default_calculator();

        // 检查运算符
        if ('=' !== $operator and !$this->has_calculator($operator)) {
            throw new Exception(self::ERR_OPERATOR, compact(['name', 'operator']));
        }

        // 〇元运算符
        if (Operator::isEmptyCalculator($operator)) return $this->null_sprite($operator);

        // 设置为NULL
        if ($operator === Operator::SET_NULL_OPERATOR and $element->canNull()) {
            return $this->null_sprite(Operator::SET_NULL_OPERATOR);
        }

        if ($operator === Operator::NOW_OPERATOR) {
            return $this->make_sprite('=', time());
        }

        if ($operator === Operator::IP_OPERATOR) {
            return $this->make_sprite('=', $input['@ip'] ?? null);
        }

        if ($operator === Operator::RAND_OPERATOR) {
            return $this->make_sprite('=', Integer::rand($element));
        }

        if ($operator instanceof Operator) {
            $exp = $operator->getStatement($value, $name, $input);
            return $exp ? $this->make_sprite(Operator::EXPRESSION_OPERATOR, $exp) : null;
        }

        $value = $value ?? $input[$name] ?? null;
        $value = $this->before_input($value);

        // 无意义的输入则跳过该项
        if ($value === null and ($element->canNull() or $element->hasDefault() or !$element->must())) return null;

        if ($value === null) throw new Exception(self::ERR_OPERAND, compact(['name', 'operator', 'value']));

        if (!$this->valid_fix($value, $operator)) {
            throw new Exception(self::ERR_OPERAND, compact(['name', 'operator', 'value']));
        }

        if ($operator === '=' && !$this->valid_rules($value)) {
            throw new Exception(self::ERR_OPERAND, compact(['name', 'operator', 'value']));
        }

        return $this->make_sprite($operator, $this->save($value));
    }

    protected function default_calculator(): string
    {
        return '=';
    }

    protected function has_calculator($operator): bool
    {
        return in_array($operator, $this->_calculators());
    }

    /**
     * 获取支持的运算符集合
     *
     * @return array
     */
    abstract protected function _calculators(): array;

    /////////////////////////////////////////////////////////////////////////////////

    /**
     * 生成条件表达式
     *
     * @param mixed|null $value
     * @param ?string    $operator
     *
     * @return Sprite
     * @throws Exception
     */
    public function compare(
        mixed   $value,
        ?string $operator,
    ): Sprite {
        $element = $this->element;
        $name = $element->name;

        if ($operator === null) $operator = $this->default_compare();

        if ($operator !== '=' && !$this->has_compare($operator))
            throw new Exception(self::ERR_OPERATOR, compact(['name', 'operator']),);

        if (Operator::isEmptyCompare($operator)) return $this->null_sprite($operator);

        if ($operator === Operator::CARD_BIRTH_GREAT || $operator === Operator::CARD_BIRTH_LESS) {
            $v = strtr($value, ['-' => '']);
            if (!is_numeric($v)) throw new Exception(
                self::ERR_OPERAND,
                compact(['name', 'operator', 'value']),
            );
            return $this->int_sprite($operator, intval($v));
        }

        if (
            $operator === Operator::NOT_NULL_OPERATOR
            || $operator === Operator::RELATION_OPERATOR
            || $operator === Operator::CONTENT_OPERATOR
        ) {
            $ops = [
                Operator::NOT_NULL_OPERATOR => Operator::IS_NULL_OPERATOR,
                Operator::RELATION_OPERATOR => Operator::INT_EMPTY_OPERATOR,
                Operator::CONTENT_OPERATOR  => Operator::STR_EMPTY_OPERATOR,
            ];
            return $this->null_sprite(!!$value ? $operator : $ops[$operator]);
        }

        //  这里要注意！！！！！！！
        if ('=' === $operator and is_array($value)) $operator = Operator::IN_OPERATOR;

        $value = $this->before_input($value);
        if (!$this->valid_fix($value, $operator, false)) {
            throw new Exception(self::ERR_OPERAND, compact(['name', 'operator', 'value']),);
        }

        return $this->make_sprite($operator, $value);
    }

    /**
     * 获取支持的比较符集合
     *
     * @return array
     */
    abstract protected function compares(): array;

    protected function default_compare(): string
    {
        return '=';
    }

    /**
     * ## 检验比较符有效性
     *
     * @param string $operator
     *
     * @return bool
     */
    protected function has_compare(string $operator): bool
    {
        return in_array($operator, $this->compares());
    }

    /**
     * 获取数字逻辑运算符
     *
     * @return array
     */
    protected function math_compares(): array
    {
        return [
            '<',
            '<=',
            '>',
            '>=',
            '=',
            '<>',
            Operator::IN_OPERATOR,
            Operator::NOT_IN_OPERATOR,
            Operator::NOT_NULL_OPERATOR,
            Operator::IS_NULL_OPERATOR,
            Operator::BETWEEN_OPERATOR,
        ];
    }

    protected function simple_compares(): array
    {
        return [
            '=',
            '<>',
            Operator::IS_NULL_OPERATOR,
            Operator::NOT_NULL_OPERATOR,
            Operator::IN_OPERATOR,
            Operator::NOT_IN_OPERATOR,
        ];
    }

    static public function getInstance(Independent $element): Pascal
    {
        if ($element->isRelation()) {
            try {
                /** @var Relation $element */
                if ($element->getRelateModel()->isUuid()) return new Uuid($element);
            } catch (Throwable) {
            }

            return new Pid($element);
        }

        if ($element->get(Type::MULTI, false)) return new Multi($element);

        return match ($element->getPascal()) {
            Type::PASSWORD => new Password($element),
            Type::PID      => new Pid($element),
            Type::MARK     => new Multi($element),
            Type::DATETIME,
            Type::DATE     => new Second($element),
            Type::TIME     => new Minute($element),
            Type::CARD     => new Card($element),
            Type::SELECT   => new Select($element),
            Type::IP       => new Ip($element),
            Type::ID       => new Id($element),
            Type::DECIMAL  => new Decimal($element),
            Type::BOOL     => new Boolean($element),
            Type::INTEGER  => new Integer($element),
            Type::TEXT     => new Text($element),
            default            => new Str($element)
        };
    }
}